#lang racket/base
(require racket/tcp)
(require racket/list)

(provide make-ircbot add-lookup! bot-channel bot-server bot-nick bot-port mesg kill send verbose)

(define PORT 6667)
(define verbose (make-parameter #t))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
#|
and ircbot is a
(bot in out thrd lookup server port channel nick)

a lookup-entry is a 
(lookup [string bot -> any] [string bot -> any])
|# 
(struct bot (in out [thread #:mutable] [lookup #:mutable] server port channel nick))
(struct lookup (check act))
(define lookups empty)

;; ircbot [string bot -> any] [string bot -> any] ->
(define (add-lookup! bot check act)
  (set-bot-lookup! bot (cons (lookup check act) (bot-lookup bot))))

;; ircbot msg ->
(define (lookup-msg raw bot)
  (define msg (substring raw 0 (sub1 (string-length raw))))
  (for ([l (bot-lookup bot)]
        #:when ((lookup-check l) msg bot))
    ((lookup-act l) msg bot)))

;; makes a bot an connects it to the server. bot will join channel when ready
(define (make-ircbot server channel nick #:port [port PORT])
  (define-values (in out) (tcp-connect server port))
  (let ([b (bot in out (thread (λ () (void))) empty server port channel nick)])
    (send (string-append "NICK " (bot-nick b)) b)
    (send (string-append "USER " (bot-nick b) " pointless * :@kaboom") b)
    (set-bot-thread! b (thread (λ () (listen b))))
    b))
  
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;; the listener on the port
(define (listen bot)
  (sync (handle-evt (bot-in bot) (λ (in) (reader bot))))
  (listen bot))

;; handles new messages 
(define (reader bot)
  (define in (bot-in bot))
  (define msg (read-line in))
  (when (verbose)
    (display "got: ")
    (displayln msg))
  (cond [(eof-object? msg)
         (kill bot)]
        [(ping? msg)
         (pong msg bot)]
        [(connect? msg bot)
         (join bot)])
  (lookup-msg msg bot))
        
;; sends a message
(define (send msg bot)
  (define out (bot-out bot))
  (displayln msg out)
  (flush-output out)
  (when (verbose)
    (displayln "sent: ")
    (write msg)
    (displayln "")))

;; kills a bot
(define (kill bot)
  (kill-thread (bot-thread bot))
  (tcp-abandon-port (bot-in bot))
  (tcp-abandon-port (bot-out bot)))

;; String OutputPort ->
;; sends a message to this channel
(define (mesg msg bot)
  (send (string-append "PRIVMSG " (bot-channel bot) " " ":" msg) bot))

;; pinging
(define (ping? m)
  (and (> (string-length m) 6)
       (string=? "PING" (substring m 0 4))))
(define (pong m bot)
  (send (string-append "PONG "
                       (substring m 5 (string-length m)))
        bot))

(define (join bot)
  (send (string-append "JOIN " (bot-channel bot)) bot))

;; string -> any
(define (connect? m bot)
  (regexp-match (regexp (string-append  "001 " (bot-nick bot)))
                m))